/* Copyright 2020 The TensorFlow Authors. All Rights Reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/

import { computed, customElement, observe, property } from '@polymer/decorators';
import { html, PolymerElement } from '@polymer/polymer';
import * as _ from 'lodash';
import '../polymer/irons_and_papers';
import { LegacyElementMixin } from '../polymer/legacy_element_mixin';
import '../tf_wbr_string/tf-wbr-string';
import * as tf_graph_scene_edge from '../tf_graph_common/edge';
import * as tf_graph from '../tf_graph_common/graph';
import * as tf_graph_hierarchy from '../tf_graph_common/hierarchy';
import * as tf_graph_scene_node from '../tf_graph_common/node';
import * as tf_graph_render from '../tf_graph_common/render';
import '../tf_graph_common/tf-node-icon';
import * as tf_graph_util from '../tf_graph_common/util';
import './tf-node-list-item';
import './node-ioinfo-item';

const matchStorage: [string, number, any[], any[]][] = [];
const unMatchStorage: [] = [];
@customElement('tf-node-info')
class TfNodeInfo extends LegacyElementMixin(PolymerElement) {
  static readonly template = html`
    <style>
      .sub-list-group {
        font-weight: 500;
        font-size: 12pt;
        padding-bottom: 8px;
        width: 100%;
      }

      .sub-list {
        overflow-y: scroll;
      }

      .attr-list-item {
        padding-left: 8px;
      }

      .attr-left {
        float: left;
        width: 30%;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
        color: var(--secondary-text-color);
        font-size: 11pt;
        font-weight: 400;
      }

      .attr-left1 {
        float: left;
        width: 100%;
        white-space: nowrap;
        color: var(--secondary-text-color);
        font-size: 11pt;
        font-weight: 400;
      }

      .attr-right {
        margin-left: 35%;
        word-wrap: break-word;
        color: var(--secondary-text-color);
        font-weight: 400;
        white-space: pre-line;
      }

      .sub-list-table {
        display: table;
        width: 100%;
      }

      .sub-list-table-row {
        display: table-row;
      }

      .sub-list-table-row .sub-list-table-cell:last-child {
        text-align: right;
      }

      .sub-list-table-cell {
        color: var(--secondary-text-color);
        display: table-cell;
        font-size: 11pt;
        font-weight: 400;
        max-width: 200px;
        padding: 0 4px;
      }

      paper-item {
        padding: 0;
        background: var(--primary-background-color);
      }

      paper-item-body[two-line] {
        min-height: 0;
        padding: 8px 12px 4px;
      }

      .expandedInfo {
        padding: 8px 12px;
      }

      .controlDeps {
        padding: 0 0 0 8px;
      }

      .node-name {
        white-space: normal;
        word-wrap: break-word;
        font-size: 14pt;
        font-weight: 500;
      }

      .node-icon {
        float: right;
      }

      .subtitle {
        color: var(--secondary-text-color);
        font-size: 12pt;
      }

      .op-label {
        display: inline-block;
        max-width: 260px;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
      }

      .controlLine {
        font-size: 11pt;
        font-weight: 400;
      }

      .toggle-button {
        float: right;
        max-height: 20px;
        max-width: 20px;
        padding: 0;
      }

      .control-toggle-button {
        float: left;
        max-height: 20px;
        max-width: 20px;
        padding: 0;
      }

      .toggle-include-group {
        padding-top: 4px;
      }

      .toggle-include {
        margin: 5px 6px;
        text-transform: none;
        padding: 4px 6px;
        font-size: 10pt;
        background-color: #fafafa;
        color: #666;
      }

      .toggle-include:hover {
        background-color: var(--google-yellow-100);
      }

      .non-control-list-item {
        padding-left: 10px;
      }

      #suggestion-content {
        width: 100%;
        padding: 10px;
        font-size: 11pt;
        font-weight: 400;
      }
    </style>
    <paper-item>
      <paper-item-body two-line>
        <div>
          <paper-icon-button icon="{{_getToggleIcon(_expanded)}}" on-click="_toggleExpanded" class="toggle-button">
          </paper-icon-button>
          <div class="node-name">
            <tf-wbr-string value="[[_getNodeName(selectedEdge, _node)]]" delimiter-pattern="/"> </tf-wbr-string>
          </div>
        </div>
        <div secondary>
          <tf-node-icon
            class="node-icon"
            node="[[_node]]"
            render-info="[[_getRenderInfo(selectedNode, renderHierarchy)]]"
            template-index="[[_templateIndex]]"
          ></tf-node-icon>
          <template is="dom-if" if="{{_node.op}}">
            <div class="subtitle">
              <span class="op-label" title="[[_node.op]]">Operation:[[_node.op]]</span>
            </div>
          </template>
          <template is="dom-if" if="{{_node.metagraph}}">
            <div class="subtitle">
              Subgraph:
              <span>[[_node.cardinality]]</span> nodes
            </div>
          </template>
        </div>
      </paper-item-body>
    </paper-item>
    <iron-collapse opened="{{_expanded}}">
      <template is="dom-if" if="{{_expanded}}" restamp="true">
        <div class="expandedInfo">
          <template is="dom-if" if="{{_suggestions}}">
            <paper-icon-button
              icon="{{_getToggleIcon(_suggestExpanded)}}"
              on-click="_toggleSuggestExpanded"
              class="toggle-button"
            >
            </paper-icon-button>
            <div class="sub-list-group">
              Suggestions
              <template is="dom-if" if="{{_suggestExpanded}}">
                <div id="suggestion-content" />
              </template>
            </div>
          </template>

          <paper-icon-button
            icon="{{_getToggleIcon(_attrExpanded)}}"
            on-click="_toggleAttrExpanded"
            class="toggle-button"
          >
          </paper-icon-button>
          <div class="sub-list-group attributes">
            Attributes (<span>[[_attributes.length]]</span>)
            <template is="dom-if" if="{{_attrExpanded}}">
              <iron-list class="sub-list" id="attributesList" items="[[_attributes]]">
                <template>
                  <div class="attr-list-item">
                    <div class="attr-left">[[item.key]]</div>
                    <div class="attr-right">[[item.value]]</div>
                  </div>
                </template>
              </iron-list>
            </template>
          </div>

          <template is="dom-if" if="{{_device}}">
            <div class="sub-list-group device">
              <div class="attr-left">Device</div>
              <div class="attr-right">[[_device]]</div>
            </div>
          </template>

          <paper-icon-button
            icon="{{_getToggleIcon(_inputsExpanded)}}"
            on-click="_toggleInputsExpanded"
            class="toggle-button"
          >
          </paper-icon-button>
          <div class="sub-list-group predecessors">
            Inputs (<span>[[_totalPredecessors]]</span>)
            <template is="dom-if" if="{{_inputsExpanded}}">
              <iron-list class="sub-list" id="inputsList" items="{{_inputsData}}">
                <template>
                  <node-ioinfo-item
                    class="non-control-list-item"
                    name="[[item.key]]"
                    attrs="[[item.value]]"
                    tooltips="[[tooltips]]"
                    on-io-item-toggle="_onInputsDataItemToggled"
                  >
                  </node-ioinfo-item>
                </template>
              </iron-list>
            </template>
            <template is="dom-if" if="[[_predecessors.control.length]]">
              <div class="controlDeps">
                <div class="controlLine">
                  <paper-icon-button
                    icon="{{_getToggleIcon(_openedControlPred)}}"
                    on-click="_toggleControlPred"
                    class="control-toggle-button"
                  >
                  </paper-icon-button>
                  Control dependencies
                </div>
                <iron-collapse opened="{{_openedControlPred}}" no-animation>
                  <template is="dom-if" if="{{_openedControlPred}}" restamp="true">
                    <iron-list class="sub-list" items="[[_predecessors.control]]">
                      <template>
                        <tf-node-list-item
                          card-node="[[_node]]"
                          item-node="[[item.node]]"
                          item-render-info="[[item.renderInfo]]"
                          name="[[item.name]]"
                          item-type="predecessors"
                          template-index="[[_templateIndex]]"
                        >
                        </tf-node-list-item>
                      </template>
                    </iron-list>
                  </template>
                </iron-collapse>
              </div>
            </template>
          </div>

          <paper-icon-button
            icon="{{_getToggleIcon(_outputsExpanded)}}"
            on-click="_toggleOutputsExpanded"
            class="toggle-button"
          >
          </paper-icon-button>
          <div class="sub-list-group successors">
            Outputs (<span>[[_totalSuccessors]]</span>)
            <template is="dom-if" if="{{_outputsExpanded}}">
              <iron-list class="sub-list" id="outputsList" items="{{_outputsData}}">
                <template>
                  <node-ioinfo-item
                    class="non-control-list-item"
                    name="[[item.key]]"
                    attrs="[[item.value]]"
                    tooltips="[[tooltips]]"
                    on-io-item-toggle="_onOutputsDataItemToggled"
                  >
                  </node-ioinfo-item>
                </template>
              </iron-list>
            </template>
            <template is="dom-if" if="[[_successors.control.length]]">
              <div class="controlDeps">
                <div class="controlLine">
                  <paper-icon-button
                    icon="{{_getToggleIcon(_openedControlSucc)}}"
                    on-click="_toggleControlSucc"
                    class="control-toggle-button"
                  >
                  </paper-icon-button>
                  Control dependencies
                </div>
                <iron-collapse opened="{{_openedControlSucc}}" no-animation>
                  <template is="dom-if" if="{{_openedControlSucc}}" restamp="true">
                    <iron-list class="sub-list" items="[[_successors.control]]">
                      <template>
                        <tf-node-list-item
                          card-node="[[_node]]"
                          item-node="[[item.node]]"
                          item-render-info="[[item.renderInfo]]"
                          name="[[item.name]]"
                          item-type="successors"
                          template-index="[[_templateIndex]]"
                        >
                        </tf-node-list-item>
                      </template>
                    </iron-list>
                  </template>
                </iron-collapse>
              </div>
            </template>
          </div>
          <paper-icon-button
            icon="{{_getToggleIcon(_stackExpanded)}}"
            on-click="_toggleStackExpanded"
            class="toggle-button"
          >
          </paper-icon-button>
          <div class="sub-list-group attributes">
            StackInfo (<span>[[_stackData.length]]</span>)
            <template is="dom-if" if="{{_stackExpanded}}">
              <iron-list class="sub-list" id="attributesList" items="[[_stackData]]">
                <template>
                  <div class="attr-list-item">
                    <div class="attr-left1">[[item]]</div>
                  </div>
                </template>
              </iron-list>
            </template>
          </div>
          <template is="dom-if" if="{{_hasDisplayableNodeStats}}">
            <div class="sub-list-group node-stats">
              Node Stats
              <div class="sub-list-table">
                <template is="dom-if" if="{{_nodeStats.totalBytes}}">
                  <div class="sub-list-table-row">
                    <div class="sub-list-table-cell">Memory</div>
                    <div class="sub-list-table-cell">[[_nodeStatsFormattedBytes]]</div>
                  </div>
                </template>
                <template is="dom-if" if="{{_getTotalMicros(_nodeStats)}}">
                  <div class="sub-list-table-row">
                    <div class="sub-list-table-cell">Compute Time</div>
                    <div class="sub-list-table-cell">[[_nodeStatsFormattedComputeTime]]</div>
                  </div>
                </template>
                <template is="dom-if" if="{{_nodeStats.outputSize}}">
                  <div class="sub-list-table-row">
                    <div class="sub-list-table-cell">Tensor Output Sizes</div>
                    <div class="sub-list-table-cell">
                      <template is="dom-repeat" items="{{_nodeStatsFormattedOutputSizes}}"> [[item]] <br /> </template>
                    </div>
                  </div>
                </template>
              </div>
            </div>
          </template>
        </div>
      </template>
    </iron-collapse>
  `;
  /**
   * Note: we intentionally avoid the property name 'nodeName', because
   * Polymer Resin does not support it. Resin's contract system prevents
   * using native HTMLElement property names unless they have an
   * explicit security contract (e.g. 'title' is allowed).
   * https://github.com/Polymer/polymer-resin/blob/master/lib/contracts/contracts.js
   */
  @property({ type: String })
  selectedNode: string;
  @property({ type: Object })
  graphHierarchy: tf_graph_hierarchy.Hierarchy;
  @property({ type: Object })
  renderHierarchy: any;
  @property({
    type: Object,
    computed: '_getNode(selectedNode, graphHierarchy)',
    observer: '_resetState',
  })
  _node: any;
  @property({
    type: Object,
    computed: '_getNodeStats(selectedNode, graphHierarchy)',
    observer: '_resetState',
  })
  _nodeStats: any;
  @property({
    type: Number,
    observer: '_nodeIncludeStateChanged',
  })
  // The enum value of the include property of the selected node.
  nodeInclude: number;
  @property({
    type: Boolean,
  })
  _expanded: boolean = true;
  @property({
    type: Boolean,
  })
  _suggestExpanded: boolean = true;
  @property({
    type: Boolean,
  })
  _attrExpanded: boolean = true;
  @property({
    type: Boolean,
  })
  _inputsExpanded: boolean = true;
  @property({
    type: Boolean,
  })
  _outputsExpanded: boolean = true;
  @property({
    type: Boolean,
  })
  _stackExpanded: boolean = true;
  @property({
    type: Boolean,
  })
  _openedControlPred: boolean = false;
  @property({
    type: Boolean,
  })
  _openedControlSucc: boolean = false;
  @property({ type: String })
  _auxButtonText: string;
  @property({ type: String })
  _groupButtonText: string;
  @property({ type: Object })
  _templateIndex: (name: string) => number | null = null!;
  @property({ type: Object })
  selectedEdge: tf_graph_render.EdgeData;
  @property({ type: Object })
  tooltips: object;
  expandNode() {
    this.fire('_node.expand', (this as any).node);
  }
  _getNode(selectedNode, graphHierarchy) {
    return graphHierarchy.node(selectedNode);
  }
  @observe('_node')
  changeSuggestion() {
    let node = this._node;
    if (node && node.suggestions && node.suggestions.text) {
      let text: string = node.suggestions.text;
      _.each(node.suggestions, (value, key) => {
        if (key !== 'text') {
          text = text.replace(new RegExp(key, 'g'), `<a href="${value}" target="_blank">${key}</a>`);
        }
      });
      setTimeout(() => {
        let textarea = this.shadowRoot?.getElementById('suggestion-content');
        if (textarea) {
          textarea.innerHTML = text;
        }
      });
    }
  }
  _getNodeStats(selectedNode, graphHierarchy) {
    var node = this._getNode(selectedNode, graphHierarchy);
    if (node) {
      return node.stats;
    }
    return null;
  }
  _getTotalMicros(stats) {
    return stats ? stats.getTotalMicros() : 0;
  }
  @computed('_nodeStats')
  get _hasDisplayableNodeStats(): boolean {
    var stats = this._nodeStats;
    return tf_graph_util.hasDisplayableNodeStats(stats);
  }
  @computed('_node')
  get _suggestions(): object | undefined {
    const node = this._node;
    if (!node || !node.suggestions || !node.suggestions.text) {
      return undefined;
    }

    return node.suggestions;
  }
  @computed('_nodeStats')
  get _nodeStatsFormattedBytes(): string | undefined {
    var stats = this._nodeStats;
    if (!stats || !stats.totalBytes) {
      return;
    }
    return tf_graph_util.convertUnitsToHumanReadable(stats.totalBytes, tf_graph_util.MEMORY_UNITS);
  }
  @computed('_nodeStats')
  get _nodeStatsFormattedComputeTime(): string | undefined {
    var stats = this._nodeStats;
    if (!stats || !stats.getTotalMicros()) {
      return;
    }
    return tf_graph_util.convertUnitsToHumanReadable(stats.getTotalMicros(), tf_graph_util.TIME_UNITS);
  }
  @computed('_nodeStats')
  get _nodeStatsFormattedOutputSizes(): unknown[] | undefined {
    var stats = this._nodeStats;
    if (!stats || !stats.outputSize || !stats.outputSize.length) {
      return;
    }
    return _.map(stats.outputSize, function (shape) {
      if (shape.length === 0) {
        return 'scalar';
      }
      return '[' + shape.join(', ') + ']';
    });
  }
  _getRenderInfo(selectedNode, renderHierarchy) {
    return this.renderHierarchy.getOrCreateRenderNodeByName(selectedNode);
  }
  _getNodeName(selectedEdge, _node) {
    if (!!selectedEdge) {
      return 'Edge';
    } else {
      if (_node.name.startsWith('N___') || _node.name.startsWith('B___')) {
        return _node.name.slice(4);
      } else {
        return _node.name;
      }
    }
  }
  _objectToArray(obj: object): Array<{ key: string; value: any }> {
    return Object.keys(obj)
    .filter((key) => key !== 'data_name')
    .map((key) => {
      let tooltip = key;
      if (tooltip in this.tooltips) {
        tooltip += `: ${this.tooltips[key]}`;
      }
      return {
        key,
        tooltip,
        value: obj[key],
      };
    });
  }
  @computed('_node', 'tooltips')
  get _inputsData(): Array<{ key: string; value: any }> {
    const node = this._node;
    let nodeName = '';
    if (node !== undefined) {
      if (this._node.name.startsWith('N___') || this._node.name.startsWith('B___')) {
        nodeName = this._node.name.slice(4);
      } else {
        nodeName = this._node.name;
      }
    }
    if (!node || !node.inputData) {
      return [];
    }
    // 深拷贝 node.outputData，以免直接修改原始数据
    const inputDataCopy = JSON.parse(JSON.stringify(node.inputData)) as any;
    let counter = 0;
    const foundMatchItem = matchStorage.find(
      (item) => Array.isArray(item) && item.length > 0 && item[0] === this._node.name
    );
    const foundUnmatchItem = matchStorage.find(
      (item) => Array.isArray(item) && item.length > 0 && item === this._node.name
    );
    if (foundMatchItem) {
      for (let key in inputDataCopy) {
        if (inputDataCopy.hasOwnProperty(key) && this._node.name.startsWith('N___')) {
          const item = inputDataCopy[key];
          if (item.hasOwnProperty('Norm') && matchStorage.length != 0 && foundMatchItem[2].length != 0) {
            for (let [key, value] of foundMatchItem[2][counter].entries()) {
              item[value[0]] = value[1];
            }
          }
        }
        counter++;
        if (counter == foundMatchItem[2].length) {
          break;
        }
      }
    }
    // 使用 outputDataCopy 生成输出，而不影响原始的 node.outputData
    return Object.keys(inputDataCopy).map((key, index) => {
      const uniqueKey = key.replace(nodeName + '.', '');
      return {
        key: uniqueKey,
        value: this._objectToArray(inputDataCopy[key]),
      };
    });
  }

  @computed('_node', 'tooltips')
  get _outputsData(): Array<{ key: string; value: any }> {
    const node = this._node;
    let nodeName = '';
    if (node !== undefined) {
      if (this._node.name.startsWith('N___') || this._node.name.startsWith('B___')) {
        nodeName = this._node.name.slice(4);
      } else {
        nodeName = this._node.name;
      }
    }
    if (!node || !node.outputData) {
      return [];
    }
    // 深拷贝 node.outputData，以免直接修改原始数据
    const outputDataCopy = JSON.parse(JSON.stringify(node.outputData)) as any;
    let counter = 0;
    const foundMatchItem = matchStorage.find(
      (item) => Array.isArray(item) && item.length > 0 && item[0] === this._node.name
    );
    if (foundMatchItem) {
      for (let key in outputDataCopy) {
        if (outputDataCopy.hasOwnProperty(key) && this._node.name.startsWith('N___')) {
          const item = outputDataCopy[key];
          if (item.hasOwnProperty('Norm') && matchStorage.length != 0 && foundMatchItem[3].length != 0) {
            for (let [key, value] of foundMatchItem[3][counter].entries()) {
              item[value[0]] = value[1];
            }
          }
        }
        counter++;
        if (counter == foundMatchItem[3].length) {
          break;
        }
      }
    }
    // 使用 outputDataCopy 生成输出，而不影响原始的 node.outputData
    return Object.keys(outputDataCopy).map((key, index) => {
      const uniqueKey = key.replace(nodeName + '.', '');
      return {
        key: uniqueKey,
        value: this._objectToArray(outputDataCopy[key]),
      };
    });
  }

  @computed('_node', 'tooltips')
  get _stackData() {
    const node = this._node;
    if (!node || !node.stackData) {
      return [];
    }
    let newStackData: string = node.stackData.slice(1, -1);
    let segments = newStackData.split(", '");
    segments = segments.map((segment) => segment.replace(/^'/, '').replace(/'$/, ''));
    return segments;
  }
  @computed('_node', 'selectedEdge', 'tooltips')
  get _attributes(): unknown[] {
    if (!!this.selectedEdge) {
      const edge = this.selectedEdge?.label.metaedge.baseEdgeList[0];
      if (!edge || !edge.attr) {
        return [];
      }
      var attrs: any[] = [];
      _.each(Object.keys(edge.attr), (key) => {
        if (key !== '_path') {
          attrs.push({
            key,
            value: JSON.stringify(edge.attr?.[key]),
          });
        }
      });
    } else {
      var node = this._node;
      this.async(this._resizeList.bind(this, '#attributesList'));
      if (!node || !node.attr) {
        return [];
      }
      var attrs: any[] = [];
      const tooltips = this.tooltips;
      if (node.name.slice(0, 4) == 'N___' || node.name.slice(0, 4) == 'B___') {
        _.each(node.attr, function (entry) {
          // Unpack the "too large" attributes into separate attributes
          // in the info card, with values "too large to show".
          if (entry.key !== tf_graph.PRECISION_INDEX) {
            let tooltip = entry.key;
            if (entry.key in tooltips) {
              tooltip += `: ${tooltips[entry.key]}`;
            }
            attrs.push({
              key: entry.key,
              tooltip,
              value:
                // 1. 当 value 为对象 { 'aaa' } 时
                typeof entry.value === 'object' && !Array.isArray(entry.value)
                  ? JSON.stringify(entry.value) // 对象转为 JSON 字符串
                  : // 2. 当 value 为一维数组 ['aaa'] 时
                  Array.isArray(entry.value) && entry.value.every((item) => !Array.isArray(item))
                  ? JSON.stringify(entry.value) // 一维数组直接转为 JSON 字符串
                  : // 3. 当 value 为二维数组时
                  Array.isArray(entry.value) && entry.value.some(Array.isArray)
                  ? JSON.stringify(entry.value.flat()) // 展平二维数组并转为 JSON 字符串
                  : // 其他情况保持原有处理
                  typeof entry.value === 'string'
                  ? entry.value
                  : JSON.stringify(entry.value),
            });
          }
        });
      } else {
        _.each(node.attr, function (entry) {
          // Unpack the "too large" attributes into separate attributes
          // in the info card, with values "too large to show".
          if (entry.key !== tf_graph.PRECISION_INDEX) {
            let tooltip = entry.key;
            if (entry.key in tooltips) {
              tooltip += `: ${tooltips[entry.key]}`;
            }
            attrs.push({
              key: entry.key,
              tooltip,
              value:
                // 1. 当 value 为对象 { 'aaa' } 时
                typeof entry.value === 'object' && !Array.isArray(entry.value)
                  ? (function () {
                      let jsonString = JSON.stringify(entry.value);

                      // 正则匹配：只匹配不在双引号内的逗号
                      const splitByCommaOutsideQuotes = (str) => {
                        const regex = /,(?=(?:[^"]*"[^"]*")*[^"]*$)/g;
                        return str.split(regex);
                      };

                      // 返回按逗号分割并换行的字符串
                      return splitByCommaOutsideQuotes(jsonString).join('\n');
                    })()
                  : // 2. 当 value 为一维数组 ['aaa'] 时
                  Array.isArray(entry.value) && entry.value.every((item) => !Array.isArray(item))
                  ? JSON.stringify(entry.value) // 一维数组直接转为 JSON 字符串
                  : // 3. 当 value 为二维数组时
                  Array.isArray(entry.value) && entry.value.some(Array.isArray)
                  ? entry.value.map((arr) =>
                      [
                        `Name: ${arr[0] || ''}\n`,
                        `Type: ${arr[1] || ''}\n`,
                        `A Core: ${arr[2] || ''}\n`,
                        `Duration(US): ${arr[3] || ''}\n`,
                        `Input Shapes: ${arr[4] || ''}\n`,
                        `Input Data Types: ${arr[5] || ''}\n`,
                      ].join('\n')
                    ) // 处理每个一维数组并添加换行符
                  : // 其他情况保持原有处理
                  typeof entry.value === 'string'
                  ? entry.value
                  : JSON.stringify(entry.value),
            });
          }
        });
      }
    }
    return attrs;
  }
  @computed('_node')
  get _device(): string {
    var node = this._node;
    return node ? node.device : null;
  }
  @computed('_node', 'graphHierarchy', 'selectedEdge')
  get _successors(): any {
    var node = this._node;
    var hierarchy = this.graphHierarchy;
    this._refreshNodeItemList('inputsList');
    if (node) {
      return this._convertEdgeListToEdgeInfoList(hierarchy.getSuccessors(node.name), false, node.isGroupNode);
    } else if (this.selectedEdge) {
      const successor: tf_graph.Edges = {
        control: [],
        regular: [this.selectedEdge.label.metaedge],
      };
      return this._convertEdgeListToEdgeInfoList(successor, false, false);
    } else {
      return { regular: [], control: [] };
    }
  }
  @computed('_node', 'graphHierarchy', 'selectedEdge')
  get _predecessors(): any {
    var node = this._node;
    var hierarchy = this.graphHierarchy;
    this._refreshNodeItemList('outputsList');
    if (node) {
      return this._convertEdgeListToEdgeInfoList(hierarchy.getPredecessors(node.name), true, node.isGroupNode);
    } else if (this.selectedEdge) {
      const predecessor: tf_graph.Edges = {
        control: [],
        regular: [this.selectedEdge.label.metaedge],
      };
      return this._convertEdgeListToEdgeInfoList(predecessor, true, false);
    } else {
      return { regular: [], control: [] };
    }
  }
  // The iron lists that enumerate ops must be asynchronously updated when
  // the data they render changes. This function triggers that update.
  _refreshNodeItemList(nodeListId) {
    this.async(this._resizeList.bind(this, `#${nodeListId}`));
  }
  _onInputsDataItemToggled() {
    this.async(this._resizeList.bind(this, '#inputsList'));
  }
  _onOutputsDataItemToggled() {
    this.async(this._resizeList.bind(this, '#outputsList'));
  }
  _convertEdgeListToEdgeInfoList(list, isPredecessor, isGroupNode) {
    var addParentNodes = (path: string, parentNodeList: string[]) => {
      const nodeNameList = path.split(tf_graph.NAMESPACE_DELIM);
      nodeNameList.pop();
      _.each(nodeNameList, (nodeName) => {
        if (!parentNodeList.includes(nodeName)) {
          parentNodeList.push(nodeName);
        }
      });
    };
    /**
     * Unpacks the metaedge into a list of base edge information
     * that can be rendered.
     */
    var unpackMetaedge = (metaedge, edgeKeyList: number[], parentNodeList: string[]) => {
      return _.map(metaedge.baseEdgeList, (baseEdge: tf_graph.BaseEdge) => {
        var name = isPredecessor ? baseEdge.v : baseEdge.w;
        name = name?.split(tf_graph.NAMESPACE_DELIM).pop();
        if (name && parentNodeList.includes(name)) {
          return undefined;
        }
        let edgeId = baseEdge.attr?.id;
        if (typeof edgeId === 'number') {
          if (edgeKeyList.includes(edgeId)) {
            return undefined;
          } else {
            edgeKeyList.push(edgeId);
          }
        }
        return {
          name: name,
          node: this._getNode(name, this.graphHierarchy),
          edgeLabel: tf_graph_scene_edge.getLabelForBaseEdge(baseEdge, this.renderHierarchy),
          renderInfo: this._getRenderInfo(name, this.renderHierarchy),
        };
      });
    };
    /**
     * Converts a list of metaedges to a list of edge information
     * that can be rendered.
     */
    var toEdgeInfoList = function (edges) {
      var edgeInfoList: any[] = [];
      let edgeIdList: number[] = [];
      let parentNodeList: string[] = [];
      _.each(edges, (metaedge) => {
        const metaName = isPredecessor ? metaedge.v : metaedge.w;
        metaName && addParentNodes(metaName, parentNodeList);
        _.each(metaedge.baseEdgeList, (baseEdge: tf_graph.BaseEdge) => {
          const name = isPredecessor ? baseEdge.v : baseEdge.w;
          name && addParentNodes(name, parentNodeList);
          let nodePath = baseEdge.attr?._path;
          if (nodePath) {
            addParentNodes(nodePath, parentNodeList);
          }
        });
      });
      _.each(edges, (metaedge) => {
        var name: string = isPredecessor ? metaedge.v : metaedge.w;
        addParentNodes(name, parentNodeList);
        // Enumerate all the base edges if the node is an OpNode, or the
        // metaedge has only 1 edge in it.
        edgeInfoList = edgeInfoList.concat(unpackMetaedge(metaedge, edgeIdList, parentNodeList));
      });
      edgeInfoList = edgeInfoList.filter((item) => !!item && !parentNodeList.includes(item.name));
      return edgeInfoList;
    }.bind(this);
    return {
      regular: toEdgeInfoList(list.regular),
      control: toEdgeInfoList(list.control),
    };
  }
  @computed('_node')
  get _subnodes(): unknown[] {
    var node = this._node;
    return node && node.metagraph ? node.metagraph.nodes() : null;
  }
  @computed('_node', 'selectedEdge')
  get _totalPredecessors(): number {
    if (!!this.selectedEdge) {
      return 0;
    } else {
      return Object.keys(this._node?.inputData ?? {}).length;
    }
  }
  @computed('_node', 'selectedEdge')
  get _totalSuccessors(): number {
    if (!!this.selectedEdge) {
      return 0;
    } else {
      return Object.keys(this._node?.outputData ?? {}).length;
    }
  }
  _toggleControlPred() {
    this._openedControlPred = !this._openedControlPred;
  }
  _toggleControlSucc() {
    this._openedControlSucc = !this._openedControlSucc;
  }
  _toggleExpanded() {
    this._expanded = !this._expanded;
  }
  _toggleSuggestExpanded() {
    this._suggestExpanded = !this._suggestExpanded;
  }
  _toggleAttrExpanded() {
    this._attrExpanded = !this._attrExpanded;
  }
  _toggleInputsExpanded() {
    this._inputsExpanded = !this._inputsExpanded;
  }
  _toggleOutputsExpanded() {
    this._outputsExpanded = !this._outputsExpanded;
  }
  _toggleStackExpanded() {
    this._stackExpanded = !this._stackExpanded;
  }
  _getToggleIcon(expanded) {
    return expanded ? 'expand-less' : 'expand-more';
  }
  _resetState() {
    this._openedControlPred = false;
    this._openedControlSucc = false;
    this.set('_groupButtonText', tf_graph_scene_node.getGroupSettingLabel(this._node));
  }
  _resizeList(selector) {
    var list = document.querySelector(selector) || this.shadowRoot?.querySelector(selector);
    if (list) {
      list.fire('iron-resize');
    }
  }
  _nodeIncludeStateChanged(include, oldInclude) {
    this.set('_auxButtonText', tf_graph.getIncludeNodeButtonString(include));
  }
  _isLibraryFunction(node) {
    // If the node name starts with this string, the node is either a
    // library function or a node within it. Those nodes should never be
    // extracted into the auxiliary scene group because they represent
    // templates for function call nodes, not ops in the graph themselves.
    return node && node.name.startsWith(tf_graph.FUNCTION_LIBRARY_NODE_PREFIX);
  }
  _isInSeries(node) {
    return tf_graph_scene_node.canBeInSeries(node);
  }
  @observe('graphHierarchy')
  _graphHierarchyChanged() {
    this._templateIndex = this.graphHierarchy.getTemplateIndex();
    this.graphHierarchy.addListener(tf_graph_hierarchy.HierarchyEvent.TEMPLATES_UPDATED, () => {
      this._templateIndex = this.graphHierarchy.getTemplateIndex();
    });
  }
}
export function getMatched(matched) {
  Object.keys(matchStorage).forEach((key) => delete matchStorage[key]);
  Object.assign(matchStorage, matched);
}
export function getUnMatched(matched) {
  Object.keys(unMatchStorage).forEach((key) => delete unMatchStorage[key]);
  Object.assign(unMatchStorage, matched);
}
